Skip to content

perf(graphql): bypass ExternalContextCreator for scalar ResolveField fast-path#3838

Open
ArielSafar wants to merge 1 commit intonestjs:masterfrom
ArielSafar:perf/graphql-resolvefield-fastpath
Open

perf(graphql): bypass ExternalContextCreator for scalar ResolveField fast-path#3838
ArielSafar wants to merge 1 commit intonestjs:masterfrom
ArielSafar:perf/graphql-resolvefield-fastpath

Conversation

@ArielSafar
Copy link

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

@ResolveField() is commonly used for simple scalar transformations (e.g., converting database values like 0/1 into boolean, mapping enum codes to display values, etc.). When such scalar field resolvers are executed on large lists (thousands of parent objects), applications can experience severe latency inflation and high CPU usage, often visible in profiling as heavy runMicrotasks activity and repeated internal lifecycle bookkeeping.

This overhead is disproportionately high relative to the trivial work done in the resolver and can turn otherwise fast queries (data fetch completes quickly) into very slow GraphQL responses due to resolver invocation overhead within @nestjs/graphql.

What is the new behavior?

This PR introduces a performance optimization for scalar @ResolveField() execution in @nestjs/graphql by reducing per-invocation overhead in the common case.

Specifically, when:

  • fieldResolverEnhancers are disabled for field resolvers (default for many production setups), and
  • the resolver is synchronous and does not require extra execution-context wrapping,

the library now uses a fast-path execution strategy that avoids unnecessary promise/microtask scheduling and reduces repeated internal lifecycle bookkeeping. This significantly improves latency and CPU utilization for queries returning large lists where scalar field resolvers are invoked thousands of times.

Benchmarks / repro (included in tests) show materially improved execution time for large list responses with scalar @ResolveField() mappings, while preserving identical resolver semantics.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

  • Motivation: real-world production case where large nested lists (6k–10k items) were slowed dramatically by trivial scalar field resolvers; profiling indicated heavy lifecycle overhead originating from the resolver invocation pipeline.
  • Benchmark / reproduction repo: benchmark-nestjs-graphql-resolve-field contains a minimal benchmark that measures the overhead of @ResolveField in @nestjs/graphql and compares it against returning data directly without field resolvers.
  • How to reproduce locally: clone the repo, run yarn, yarn build, then in benchmark-service run npm install and execute ./benchmark-compare.sh (after chmod +x). The script runs the benchmark against the published @nestjs/[email protected], then swaps in a locally built/optimized package and runs again, printing a comparison.
  • Reported results in the repo README: before optimization (@nestjs/graphql v13.2.4) the benchmark shows ~30% mean overhead for @ResolveField; after the optimization, the overhead is ~1% and the difference is not statistically significant (Welch’s t-test p ≈ 0.31).
  • Added tests coverage to ensure the resolver fast-path preserves behavior for:
    • synchronous scalar resolvers
    • resolvers with arguments/context/info
    • enhanced resolvers (guards/interceptors/filters) continuing to use the existing execution path

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant